/**
 * Copyright (c) 2022 Presto Labs Pte. Ltd.
 * Author: Ju
 */

#include "coin2/controller/tcp_client.h"

#include <glog/logging.h>

#include "coin2/base/time.h"

namespace {
static const uint64_t kOneSeconds = 1000000000;
}
namespace coin2::controller {

TcpClient::TcpClient(boost::asio::io_context *ioc,
                     MessageAssembler::Callback *callback)
    : ioc_(ioc), timer_(*ioc_), msg_assembler_(callback) {}

void TcpClient::Connect(std::string ip, uint16_t port) {
  tcp_connection_ = std::make_shared<TcpConnection>(ioc_);
  tcp_connection_->RegisterCallback(this);
  ip_ = ip;
  port_ = port;
  last_connect_ts_ = impl::GetCurrentTimestamp();
  tcp_connection_->Connect(std::move(ip), port);
}

void TcpClient::SendMsg(memory::MemSlice buffer) {
  if (tcp_connection_->IsConnect()) {
    tcp_connection_->SendMsg(std::move(buffer));
  } else {
    q_.push(std::move(buffer));
  }
}

void TcpClient::OnConnect(std::weak_ptr<TcpConnection> conn, bool is_connect) {
  LOG(INFO) << "OnConnect: " << is_connect;
  auto tcp_conn = conn.lock();
  if (is_connect) {
    tcp_connection_->Start();
    tcp_connection_->SendMsg(register_info_);
    if (!q_.empty()) {
      auto buffer = q_.front();
      q_.pop();
      tcp_connection_->SendMsg(std::move(buffer));
    }
  } else {
    RetryConnect();
  }
}

void TcpClient::RetryConnect() {
  uint64_t now = impl::GetCurrentTimestamp();
  if (now < last_connect_ts_) {
    LOG(INFO) << "waiting to reconnect, please try again later";
    return;
  }
  if (now - last_connect_ts_ > 15 * kOneSeconds) {
    LOG(INFO) << "RetryConnect";
    tcp_connection_->Close();
    last_connect_ts_ = now;
    tcp_connection_->Connect(ip_, port_);
  } else {
    last_connect_ts_ = now + 15 * kOneSeconds;
    LOG(INFO) << "RetryConnect 15s later";
    timer_.expires_from_now(boost::posix_time::seconds(15));
    timer_.async_wait([this](const boost::system::error_code &ec) {
      tcp_connection_->Close();
      tcp_connection_->Connect(ip_, port_);
    });
  }
}

void TcpClient::OnDataSent(uint32_t len) {
  (void)len;
  // LOG(INFO) << "On data sent len: " << len;
  // It's just used to control the transmission speed
  if (!q_.empty()) {
    auto buffer = q_.front();
    q_.pop();
    tcp_connection_->SendMsg(std::move(buffer));
  }
}

void TcpClient::OnError(std::weak_ptr<TcpConnection> conn,
                        boost::system::error_code ec) {
  (void)conn;
  (void)ec;
  RetryConnect();
}

void TcpClient::OnRawDataReceived(std::weak_ptr<TcpConnection> conn,
                                  memory::MemSlice buffer, uint32_t len) {
  (void)conn;
  (void)len;
  msg_assembler_.OnPacketReceived(std::move(buffer));
}

}  // namespace coin2::controller